﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Actors;
using System.Threading;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Collections.Concurrent;
using System.Cloud.Collections;

namespace ActorStressTests
{
    static class ListTests
    {
        internal static void EnsureListCreated(string fabricAddress, string listName, int numReplicas=2, bool asStateless = false, int numPartitions=1)
        {
            if (asStateless)
            {
                string actorName = "fabric:/actor/stateless/" + listName;
                try
                {
                    FabricActorClient.CreateStatelessActor(fabricAddress, actorName, StatelessActorStateTypes.InMemory, StatelessActorPersonalities.List, numPartitions);
                }
                catch (Exception e)
                {
                    // Assume that this is due to the actor already having been created
                    Console.WriteLine("Warning: Exception encountered when creating stateless list: {0}", e);
                }
            }
            else
            {
                string actorName = "fabric:/actor/list/" + listName;
                try
                {
                    FabricActorClient.CreateActor(fabricAddress, "fabric:/actor/list", actorName, "ListActorReplica", numReplicas, numPartitions);
                }
                catch (Exception)
                {
                    // Assume that this is due to the actor already having been created
                    //Console.WriteLine("Warning: Exception encountered when creating list: {0}", e);
                }
            }
        }

        #region Direct List Actor tests

        //
        // Tests that our logic around list creation is resilient.
        // (It is somewhat stressful to create an actor then immediately send a mutating command to it.  There are
        // are some pitfalls involving insufficient quorum, odd faults, etc... that the client must be resilient against.)
        //
        public static void ListCreateUseTest(string fabricAddress, bool useGateway, int nActors)
        {
            Console.WriteLine("Running ListCreateUseTest({0})", nActors);

            bool timeToDie = false;

            // Have a background task that creates and destroys a service.
            // This increases the chance of a "Not Primary" error as the
            // fabric shuffles to compensate.
            var bgTask = Task.Factory.StartNew(() =>
            {
                string listName2 = "EphemeralList";
                string actorName2 = "fabric:/actor/list/" + listName2;

                while (!timeToDie)
                {
                    for (int j = 0; j < 5; j++)
                    {
                        EnsureListCreated(fabricAddress, listName2 + j, 3);
                    }

                    for (int j = 0; j < 5; j++)
                    {
                        FabricActorClient.DeleteActor(fabricAddress, actorName2+j);
                    }
                }
            });

            for (int i = 0; i < nActors; i++)
            {
                string listname = "CreateUseList" + i;
                string actorName = "fabric:/actor/list/" + listname;
                Uri fabricUri = new Uri(@"net.tcp://" + fabricAddress);
                Uri actorUri = new Uri(actorName);

                EnsureListCreated(fabricAddress, listname, 3); // 3 replicas much more stressful than 2
                using(var listClient = new FabricActorClient(fabricUri, actorUri, useGateway))
                {
                    for (int idx = 0; idx < 5; idx++)
                    {
                        var result = listClient.CallMethod<int>("Push", new object[] { listname, "right", "xxx" });
                        Stress.Assert(result == idx+1, "Expected a result of " + (idx+1) + " back from initial Push.  Result was: " + result + "  List name: " + listname);
                        Thread.Sleep(10);
                    }
                }

                FabricActorClient.DeleteActor(fabricAddress, actorName);

            }

            Console.WriteLine("Waiting for bg task to complete...");
            timeToDie = true;
            bgTask.Wait();
        }


        //
        // Does a number of concurrent pushes to grow the list, then a number of concurrent pops to shrink the list to 0.
        // Checks that the proper element is returned on the pops.
        //
        public static void ListPushPopTest1(string fabricAddress, bool useGateway, int nTasks, int nElements, string side, bool asStateless = false)
        {
            Contract.Requires(side.Equals("left", StringComparison.InvariantCultureIgnoreCase) || side.Equals("right", StringComparison.InvariantCultureIgnoreCase));
            Console.WriteLine("Running ListPushPopTest1(nTasks={0},nElements={1},side={2},asStateless={3})...", nTasks, nElements, side, asStateless);
            string listName = "stresslist";
            string actorName = asStateless ? ("fabric:/actor/stateless/" + listName) : ( "fabric:/actor/list/" + listName);
            Uri fabricUri = new Uri(@"net.tcp://" + fabricAddress);
            Uri actorUri = new Uri(actorName);

            EnsureListCreated(fabricAddress, listName, 2, asStateless);

            // Clear our list
            var outerClient = new FabricActorClient(fabricUri, actorUri, useGateway);
            object outerResult;
            Exception outerException;
            Stress.Assert(outerClient.CallMethodNoThrow("TryConstruct", new object[] { listName }, out outerResult, out outerException), "Clear FAILED: " + outerResult);
            Stress.Assert(outerClient.CallMethodNoThrow("Clear", new object[] { listName }, out outerResult, out outerException), "Clear FAILED: " + outerResult);

            // This is the action that each task will run.
            Action testAction = () =>
            {
                // Instantiate a client
                using (FabricActorClient client = new FabricActorClient(fabricUri, actorUri, useGateway))
                {
                    // Write out "xxx" strings
                    for (int i = 0; i < nElements; i++)
                    {
                        int result;
                        Exception exception;
                        bool success = client.CallMethodNoThrow("Push", new object[] { listName, side, "xxx" }, out result, out exception);
                        if(!success)
                            Stress.Assert(false, "Push op FAILED: " + exception.Message);
                    }

                    // Read back strings
                    for (int i = 0; i < nElements; i++)
                    {
                        string result;
                        Exception exception;
                        bool success = client.CallMethodNoThrow("Pop", new object[] { listName, side }, out result, out exception);
                        if(!success)
                            Stress.Assert(false, "Pop op FAILED: " + exception.Message);
                        Stress.Assert(result != null, "Expected non-NULL result from Pop");
                        Stress.Assert(result is string && ((string)result) == "xxx", "Expected 'xxx' to be returned");
                    }
                }
            };

            // Set up our actions array
            var actions = new Action[nTasks];
            for (int i = 0; i < nTasks; i++) actions[i] = testAction;

            // Run our actions in parallel
            Parallel.Invoke(actions);

            // Check that remaining length is 0
            int lengthResult;
            Stress.Assert(outerClient.CallMethodNoThrow("Length", new object[] { listName }, out lengthResult, out outerException), "Length FAILED: " + outerResult);
            Stress.Assert(lengthResult == 0, "Expected final length to be 0, was " + lengthResult);

            outerClient.Dispose();

            Console.WriteLine("ListPushPopTest1 finished SUCCESSFULLY");
        }

        //
        // Pushes assorted values onto a list and ensures that the proper number of each are read back
        //
        public static void ListPushPopTest2(string fabricAddress, bool useGateway, int nTasks, int nElements, string side, bool asStateless = false)
        {
            Contract.Requires(side.Equals("left", StringComparison.InvariantCultureIgnoreCase) || side.Equals("right", StringComparison.InvariantCultureIgnoreCase));
            Console.WriteLine("Running ListPushPopTest2(nTasks={0},nElements={1},side={2},asStateless={3})...", nTasks, nElements, side, asStateless);
            string listName = "stresslist";
            string actorName = asStateless ? ("fabric:/actor/stateless/" + listName) : ( "fabric:/actor/list/" + listName);
            Uri fabricUri = new Uri(@"net.tcp://" + fabricAddress);
            Uri actorUri = new Uri(actorName);

            // Items to be written out on each iteration
            var values = new string[] { "aaa", "bbbb", "ccc", "four score and twenty years ago", "ddddd" };
            // Keeps track of items read back
            var scorecard = new ConcurrentDictionary<string, int>();

            EnsureListCreated(fabricAddress, listName, 2, asStateless);

            // Clear our list
            var outerClient = new FabricActorClient(fabricUri, actorUri, useGateway);
            object outerResult;
            Exception outerException;
            Stress.Assert(outerClient.CallMethodNoThrow("TryConstruct", new object[] { listName }, out outerResult, out outerException), "Clear FAILED: " + outerResult);
            Stress.Assert(outerClient.CallMethodNoThrow("Clear", new object[] { listName }, out outerResult, out outerException), "Clear FAILED: " + outerResult);

            // This is the action that will be run by each parallel task
            Action testAction = () =>
            {
                using (FabricActorClient client = new FabricActorClient(fabricUri, actorUri, useGateway))
                {
                    // Write out all strings in "values"
                    for (int i = 0; i < nElements; i++)
                    {
                        int result;
                        Exception exception;
                        foreach (var payload in values)
                        {
                            var success = client.CallMethodNoThrow("Push", new object[] { listName, side, payload }, out result, out exception);
                            if(!success)
                                Stress.Assert(false, "Push op FAILED: " + exception.Message);
                        }
                    }

                    // Read back strings
                    int numToRead = values.Length;
                    for (int i = 0; i < nElements; i++)
                    {
                        string result;
                        Exception exception;
                        for (int j = 0; j < numToRead; j++)
                        {
                            var success = client.CallMethodNoThrow("Pop", new object[] { listName, side }, out result, out exception);
                            if (!success)
                            {
                                Stress.Assert(false, "Pop op FAILED: " + exception.Message);
                            }
                            scorecard.AddOrUpdate((string)result, 1, (val, prevCount) => prevCount + 1);
                        }
                    }
                }
            };

            // Set up our actions array
            var actions = new Action[nTasks];
            for (int i = 0; i < nTasks; i++) actions[i] = testAction;

            // Run our actions in parallel
            Parallel.Invoke(actions);

            // Check that remaining length is 0
            int lengthResult;
            Stress.Assert(outerClient.CallMethodNoThrow("Length", new object[] { listName }, out lengthResult, out outerException), "Length FAILED: " + lengthResult);
            Stress.Assert(lengthResult == 0, "Expected final length to be 0, was " + lengthResult);

            // Check our scorecard
            Stress.Assert(scorecard.Count == values.Length, String.Format("Expected scorecard.Count ({0}) to be values.Length ({1}), but they are different", scorecard.Count, values.Length));
            int expectedValueCount = nElements * nTasks;
            foreach (var kvp in scorecard)
            {
                Stress.Assert(kvp.Value == expectedValueCount, String.Format("Expected count for {0} to be {1}, but it is {2}", kvp.Key, expectedValueCount, kvp.Value));
            }

            outerClient.Dispose();
            Console.WriteLine("ListPushPopTest2 finished SUCCESSFULLY");
        }

        //
        // Tests contention/stress on InsertAt, UpdateItemAt and RemoveAt.
        //
        public static void InsertUpdateRemoveTest(string fabricAddress, bool useGateway, int nTasks, int nElements, int filler, int contentionIndex)
        {
            Console.WriteLine("Running InsertUpdateRemoveTest(nTasks={0},nElements={1},filler={2},contentionIndex={3})...", nTasks, nElements, filler, contentionIndex);
            string listName = "stresslist";
            string actorName = "fabric:/actor/list/" + listName;
            Uri fabricUri = new Uri(@"net.tcp://" + fabricAddress);
            Uri actorUri = new Uri(actorName);
            bool updateSeen = false;

            EnsureListCreated(fabricAddress, listName);

            // Instantiate our client, clear the list and then add the desired pre-filled elements
            var outerClient = new FabricActorClient(fabricUri, actorUri, useGateway);
            object outerResult;
            Exception outerException;
            Stress.Assert(outerClient.CallMethodNoThrow("TryConstruct", new object[] { listName }, out outerResult, out outerException), "Clear FAILED: " + outerResult);
            Stress.Assert(outerClient.CallMethodNoThrow("Clear", new object[] { listName }, out outerResult, out outerException), "Clear FAILED: " + outerResult);
            for (int i = 0; i < filler; i++)
            {
                Stress.Assert(outerClient.CallMethodNoThrow("Push", new object[] { listName, "right", "xxx" }, out outerResult, out outerException),
                                "Filler PushOp FAILED: " + outerResult.ToString());
            }

            // This is the action that each task will run
            Action testAction = () =>
            {
                // Instantiate a client
                using (FabricActorClient client = new FabricActorClient(fabricUri, actorUri, useGateway))
                {
                    // Insert and then update a string at the contention index
                    for (int i = 0; i < nElements; i++)
                    {
                        object result;
                        Exception exception;
                        bool success = client.CallMethodNoThrow("InsertAt", new object[] { listName, contentionIndex, "yyy" }, out result, out exception);
                        if(!success) Stress.Assert(false, "InsertAt op FAILED: " + exception.Message);
                        success = client.CallMethodNoThrow("UpdateItemAt", new object[] { listName, contentionIndex, "zzz" }, out result, out exception);
                        if(!success) Stress.Assert(false, "UpdateAt op FAILED: " + exception.Message);
                    }

                    // Now delete at the contentionIndex
                    for (int i = 0; i < nElements; i++)
                    {
                        string sResult;
                        Exception exception;
                        bool success = client.CallMethodNoThrow("RemoveAt", new object[] { listName, contentionIndex }, out sResult, out exception);
                        if(!success) Stress.Assert(false, "RemoveAt op FAILED: " + exception.Message);
                        Stress.Assert(sResult == "yyy" || sResult == "zzz", "Expected removed item to be 'yyy' or 'zzz', got " + sResult);
                        if (sResult == "zzz") updateSeen = true; // record the fact that we read back an updated value
                    }
                }
            };

            // Set up our actions array
            var actions = new Action[nTasks];
            for (int i = 0; i < nTasks; i++) actions[i] = testAction;

            // Run our actions in parallel
            Parallel.Invoke(actions);

            // Check that remaining length is "filler"
            int lengthResult;
            Stress.Assert(outerClient.CallMethodNoThrow("Length", new object[] { listName }, out lengthResult, out outerException), "Length FAILED: " + outerResult);
            Stress.Assert(lengthResult == filler,
                String.Format("Expected length after Invoke to be {0}, was {1}", filler, lengthResult));

            // Remove the filler items, checking that they are the correct value
            for (int i = 0; i < filler; i++)
            {
                string sResult;
                Stress.Assert(outerClient.CallMethodNoThrow("Pop", new object[] { listName, "left" }, out sResult, out outerException),
                            "Pop call failed in clean-up");
                Stress.Assert(sResult.Equals("xxx"), "Expected filler items to be 'xxx', got " + sResult);
            }

            outerClient.Dispose();
            Stress.Assert(updateSeen, "Expected to retrieve at least one updated value, did not");
            Console.WriteLine("InsertUpdateRemoveTest finished SUCCESSFULLY");
        }

        //
        // Tests contention/stress on simply adding a bunch of items.  Meant for testing recovery from primary failure.
        //
        public static void ListAddTest(string fabricAddress, bool useGateway, int nTasks, int nElements)
        {
            Console.WriteLine("Running ListAddTest(nTasks={0},nElements={1})...", nTasks, nElements);
            string listName = "stresslist";
            string actorName = "fabric:/actor/list/" + listName;
            Uri fabricUri = new Uri(@"net.tcp://" + fabricAddress);
            Uri actorUri = new Uri(actorName);

            EnsureListCreated(fabricAddress, listName);

            // Instantiate our client, clear the list and then add the desired pre-filled elements
            var outerClient = new FabricActorClient(fabricUri, actorUri, useGateway);
            object outerResult;
            int lengthResult;
            Exception outerException;
            Stress.Assert(outerClient.CallMethodNoThrow("TryConstruct", new object[] { listName }, out outerResult, out outerException), "Clear FAILED: " + outerResult);
            Stress.Assert(outerClient.CallMethodNoThrow("Clear", new object[] { listName }, out outerResult, out outerException), "Clear FAILED: " + outerResult);
            Stress.Assert(outerClient.CallMethodNoThrow("Length", new object[] { listName }, out lengthResult, out outerException), "Initial Length FAILED: " + lengthResult);
            Stress.Assert(lengthResult == 0, "Expected initial length to be 0, got " + lengthResult);

            // This is the action that each task will run
            Action testAction = () =>
            {
                // Instantiate a client
                using (FabricActorClient client = new FabricActorClient(fabricUri, actorUri, useGateway))
                {
                    // Add a bunch of elements
                    for (int i = 0; i < nElements; i++)
                    {
                        object result;
                        Exception exception;
                        bool success = client.CallMethodNoThrow("InsertAt", new object[] { listName, 0, "xxx" }, out result, out exception);
                        Stress.Assert(success, "InsertAt op FAILED: " + result);
                    }
                }

            };

            // Set up our actions array
            var actions = new Action[nTasks];
            for (int i = 0; i < nTasks; i++) actions[i] = testAction;

            // Run our actions in parallel
            Parallel.Invoke(actions);

            // Now check the length of the list
            int expectedLength = nTasks * nElements;
            List<object> listResult;
            Stress.Assert(outerClient.CallMethodNoThrow("Enumerate", new object[] {listName, 0, -1}, out listResult, out outerException), "Final Enumerate failed: " + listResult);
            int lengthOfEnumeration = listResult.Count;
            Stress.Assert(outerClient.CallMethodNoThrow("Length", new object[] { listName }, out lengthResult, out outerException), "Final Length FAILED: " + lengthResult);
            Stress.Assert(lengthResult == expectedLength,
                String.Format("Expected length after Invoke to be {0}, was {1} (Enumeration length = {2})", expectedLength, lengthResult, lengthOfEnumeration));
            Stress.Assert(lengthOfEnumeration == expectedLength, String.Format("Expected enumeration to have length {0}, got {1}", expectedLength, lengthOfEnumeration));

            Stress.Assert(outerClient.CallMethodNoThrow("Clear", new object[] { listName }, out outerResult, out outerException), "Final Clear FAILED: " + outerResult);
            outerClient.Dispose();
            Console.WriteLine("ListAddTest finished SUCCESSFULLY");
        }

        #endregion Direct List Actor tests

        #region CloudList<T> tests

        // Very basic tests on a CloudList<String>.
        public static void TestCloudListOfString(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Testing CloudList<String>");
            CloudList<String> cloudStringList = new CloudList<String>(fabricAddress, "MyStringList", useGateway);
            if (cloudStringList.CountAsync.Result > 0)
                cloudStringList.ClearAsync().Wait();
            cloudStringList.AddAsync("Hello").Wait();
            cloudStringList.AddAsync("World").Wait();
            int count = cloudStringList.CountAsync.Result;
            Console.WriteLine("List length: {0}", count);
            if (count != 2)
                throw new Exception(String.Format("After adding 2 items, count should have been 2, but was {0}", count));
            bool sawHello = false;
            bool sawWorld = false;
            int numFound = 0;
            Console.WriteLine("Enumerator: ");
            foreach (String s in cloudStringList)
            {
                Console.Write("{0}  ", s);
                if (s == "Hello")
                    sawHello = true;
                if (s == "World")
                    sawWorld = true;
                numFound++;
            }
            Console.WriteLine();
            if (!sawHello || !sawWorld)
                throw new Exception(String.Format("Enumerator didn't return expected values of Hello and World!  sawHello: {0}  sawWorld: {1}", sawHello, sawWorld));
            if (numFound != 2)
                throw new Exception(String.Format("Enumerator should have returned precisely two values, but didn't!  Num found: {0}", numFound));

            Console.WriteLine("More interesting testing on a CloudList<String>...");
            StringListTest(cloudStringList);

            int oldCount = cloudStringList.Count;
            cloudStringList.InsertAsync(0, "new first element").Wait();
            if (cloudStringList.Count != oldCount + 1)
                throw new Exception(String.Format("InsertAsync should have bumped up the collection count by one, but didn't.  new count: {0}", cloudStringList.Count));
            if (cloudStringList[0] != "new first element")
                throw new Exception(String.Format("InsertAsync didn't insert the right value.  Item at index 0 is {0}", cloudStringList[0]));

            cloudStringList.Destroy();
            Console.WriteLine("Testing CloudList<String> done.");
        }

        private static void StringListTest(CloudList<String> stringList)
        {
            if (stringList.CountAsync.Result != 0)
                stringList.Clear();

            Task task1 = stringList.AddAsync("first");
            Task task2 = stringList.AddAsync("second");
            Task task3 = stringList.AddAsync("third");
            Task.WaitAll(task1, task2, task3);
            // We now know all three strings are added, but not which order.

            stringList.SortAsync().Wait();

            String[] contents = stringList.ToArrayAsync().Result;
            if (contents[0] != "first")
                throw new Exception("First element should have been the string first.  Got: " + contents[0]);
            if (contents[1] != "second")
                throw new Exception("Second element should have been the string second.  Got: " + contents[1]);
            if (contents[2] != "third")
                throw new Exception("Third element should have been the string third.  Got: " + contents[2]);

            if (!stringList.Contains("first"))
                throw new Exception("While the string \"first\" was still in the list, Contains said it wasn't in the list!");

            stringList.Remove("first");
            if (stringList.Count != 2)
                throw new Exception("List's count should have been 2, but was " + stringList.Count);
            if (stringList.Contains("first"))
                throw new Exception("After removing the string \"first\", Contains says it was still in the list!");

            stringList.Insert(1, "2.5");
            if (stringList.Count != 3)
                throw new Exception("List's count should have been 3, but was " + stringList.Count);
            if (!String.Equals(stringList[1], "2.5"))
                throw new Exception("After inserting the string \"2.5\", the indexer should have said the second string was 2.5!");
        }

        public static void TestNumericTypes(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Testing numeric types, which serialize oddly to JSON");
            // Javascript has a number type that is either a float or an integer, and is between +/- 2^53.

            Console.WriteLine("  Testing CloudList<Int32>");
            CloudList<int> intList = new CloudList<int>(fabricAddress, "Int32TypeList", useGateway);
            if (intList.CountAsync.Result > 0)
                intList.Clear();
            intList.AddAsync(new int[] { 0, 1, 2, 3, -4 }).Wait();
            Stress.AssertAreEqual(5, intList.Count, "After adding 5 items, intList count should have been 5");
            Object obj = intList[1];
            Stress.AssertAreEqual(typeof(Int32), obj.GetType(), "When retrieving one element from a list of type Int32, expected to get an Int32 back on the client!");
            Stress.AssertAreEqual((Int32)1, obj, "Expected the value of the number to be 1.");
            int[] intArray = intList.ToArrayAsync().Result;
            for (int i = 0; i < intList.Count; i++)
            {
                int sign = (i == 4) ? -1 : 1;
                Stress.AssertAreEqual(sign * i, intArray[i], "ToArrayAsync returned an array with the incorrect values.");
                Stress.AssertAreEqual(sign * i, intList[i], "The indexer returned the wrong value and/or type of element.");
            }

            Console.WriteLine("  Testing CloudList<Int64>");
            CloudList<long> longList = new CloudList<long>(fabricAddress, "Int64TypeList", useGateway);
            if (longList.CountAsync.Result > 0)
                longList.Clear();
            longList.AddAsync(new long[] { 0, 1, 2, 3, -4 }).Wait();
            Stress.AssertAreEqual(5, longList.Count, "After adding 5 items, longList count should have been 5");
            obj = longList[1];
            Stress.AssertAreEqual(typeof(Int64), obj.GetType(), "When retrieving one element from a list of type Int64, expected to get an Int64 back on the client!");
            Stress.AssertAreEqual((Int64)1, obj, "Expected the value of the number to be 1.");
            long[] longArray = longList.ToArrayAsync().Result;
            for (long i = 0; i < longList.Count; i++)
            {
                long sign = (i == 4) ? -1 : 1;
                Stress.AssertAreEqual(sign * i, longArray[i], "ToArrayAsync returned an array with the incorrect values.");
                Stress.AssertAreEqual(sign * i, longList[(int)i], "The indexer returned the wrong value and/or type of element.");
            }

            Console.WriteLine("  Testing CloudList<UInt32>");
            CloudList<uint> uintList = new CloudList<uint>(fabricAddress, "UInt32TypeList", useGateway);
            if (uintList.CountAsync.Result > 0)
                uintList.Clear();
            uintList.AddAsync(new uint[] { 0, 1, 2, 3, 4 }).Wait();
            Stress.AssertAreEqual(5, uintList.Count, "After adding 5 items, uintList count should have been 5");
            obj = uintList[1];
            Stress.AssertAreEqual(typeof(UInt32), obj.GetType(), "When retrieving one element from a list of type UInt32, expected to get an UInt32 back on the client!");
            Stress.AssertAreEqual((UInt32)1, obj, "Expected the value of the number to be 1.");
            uint[] uintArray = uintList.ToArrayAsync().Result;
            for (uint i = 0; i < (uint)uintList.Count; i++)
            {
                Stress.AssertAreEqual(i, uintArray[i], "ToArrayAsync returned an array with the incorrect values.");
                Stress.AssertAreEqual(i, uintList[(int)i], "The indexer returned the wrong value and/or type of element.");
            }

            Console.WriteLine("  Testing CloudList<byte>");
            CloudList<byte> byteList = new CloudList<byte>(fabricAddress, "ByteTypeList", useGateway);
            if (byteList.CountAsync.Result > 0)
                byteList.Clear();
            byteList.AddAsync(new byte[] { 0, 1, 2, 3, 4 }).Wait();
            Stress.AssertAreEqual(5, byteList.Count, "After adding 5 items, byteList count should have been 5");
            obj = byteList[1];
            Stress.AssertAreEqual(typeof(Byte), obj.GetType(), "When retrieving one element from a list of type Byte, expected to get an Byte back on the client!");
            Stress.AssertAreEqual((Byte)1, obj, "Expected the value of the number to be 1.");
            byte[] byteArray = byteList.ToArrayAsync().Result;
            for (byte i = 0; i < byteList.Count; i++)
            {
                Stress.AssertAreEqual(i, byteArray[i], "ToArrayAsync returned an array with the incorrect values.");
                Stress.AssertAreEqual(i, byteList[i], "The indexer returned the wrong value and/or type of element.");
            }

            // Do various, incomplete amounts of cleanup to ensure they work.
            byteList.Dispose();
            byteList.Dispose();
            uintList.Destroy();
            uintList.Dispose();
            intList.Dispose();
            // "Leaking" the longList
        }

#if CUSTOM_PARTITIONING_AND_CONNECTION_SHARING
        public static void CloudListPartitioningCreationPerfTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("CloudList Partitioning Creation perf test");
            const int numPartitions = 10;
            Func<String, String> partitioner = listName => "listPartition" + ((uint)listName.GetHashCode()) % numPartitions;

            // Perf hack - Parallel.For will take a long time unless we up the number of threads used by the threadpool.
            // To see the difference, comment this line out then run this test again, with basically 0 pause between one run & the next.
            ThreadPool.SetMinThreads(20, 20);

            int numLists = 1000;
            Console.WriteLine("Creating {0} lists, across {1} partitions.", numLists, numPartitions);

            Stopwatch sw = new Stopwatch();
            sw.Start();

            /*
            // The sequential code takes about 1 - 2 seconds for 1000 lists, total (when running on a development 5 node cluster on the same box, in VS).
            for (int listNum = 0; listNum < numLists; listNum++)
            {
                CloudList<int> list = new CloudList<int>(fabricAddress, "list Creation " + listNum, partitioner, useGateway);
                list.Dispose();
            }

            sw.Stop();
            Console.WriteLine("Created and disposed {0} lists in {1:0.0} seconds.  {2:0.0} ms/list", numLists, sw.Elapsed.TotalSeconds, ((float)sw.ElapsedMilliseconds) / numLists);
            Thread.Sleep(1000);
            sw.Restart();
            */

            // Parallel.For takes a long time if a debugger is attached to the process.
            // Even when using ThreadPool.SetMinThreads(30, 30) the code below takes ~25 seconds to create 1000 lists inside VS.
            // Outside of VS, it takes 7 - 10 seconds.
            Console.WriteLine("Parallel.For");
            Parallel.For(0, numLists, listNum => {
                //if (listNum % 100 == 0)
                //    Console.WriteLine("  On list {0}", listNum);
                CloudList<int> list = new CloudList<int>(fabricAddress, "list Creation "+listNum, partitioner, useGateway);
                list.Dispose();
            });
            
            sw.Stop();
            Console.WriteLine("Created and disposed {0} lists in {1:0.0} seconds.  {2:0.0} ms/list", numLists, sw.Elapsed.TotalSeconds, ((float)sw.ElapsedMilliseconds) / numLists);
            Thread.Sleep(1000);
            sw.Restart();
            
            /*
            Console.WriteLine("Manual threading attempt!");
            // This code takes 81 seconds to run with a debugger attached, and 3 - 4 seconds with no debugger attached.
            // (Tested using ConditionalWeakTable in CloudList to cache socket connections)
            Thread[] threads = new Thread[numLists];
            for (int i = 0; i < numLists; i++)
            {
                threads[i] = new Thread((state) =>
                {
                    int listNum = (int)state;
                    //if (listNum % 100 == 0)
                    //    Console.WriteLine("  On list {0}", listNum);
                    CloudList<int> list = new CloudList<int>(fabricAddress, "list Creation " + listNum, partitioner, useGateway);
                    list.Dispose();
                });
                threads[i].Start(i);
            }
            foreach (Thread t in threads)
                t.Join();
            */

            sw.Stop();
            Console.WriteLine("Created and disposed {0} lists in {1:0.0} seconds.  {2:0.0} ms/list",
                              numLists, sw.Elapsed.TotalSeconds, ((float)sw.ElapsedMilliseconds) / numLists);
        }
#endif

        public static void CloudListPartitioningWinFabStyleCreationPerfTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("CloudList Windows Fabric style Partitioning Creation perf test");
            const int numPartitions = 10;

            int numLists = 1000;
            Console.WriteLine("Creating {0} lists, across {1} partitions.", numLists, numPartitions);

            Stopwatch sw = new Stopwatch();
            sw.Start();

            /*
            for (int listNum = 0; listNum < numLists; listNum++)
            {
                CloudList<int> list = new CloudList<int>(fabricAddress, "list WinFabPart Group", "list Creation " + listNum, numPartitions, useGateway);
                list.Dispose();
            }

            sw.Stop();
            Console.WriteLine("Created and disposed {0} lists in {1:0.0} seconds.  {2:0.0} ms/list", numLists, sw.Elapsed.TotalSeconds, ((float)sw.ElapsedMilliseconds) / numLists);
            Thread.Sleep(1000);
            sw.Restart();
            */

            // Parallel.For takes a long time if a debugger is attached to the process.
            /*
            Console.WriteLine("Parallel.For");
            Parallel.For(0, numLists, listNum =>
            {
                //if (listNum % 100 == 0)
                //    Console.WriteLine("  On list {0}", listNum);
                CloudList<int> list = new CloudList<int>(fabricAddress, "list WinFabPart Group", "list Creation " + listNum, numPartitions, useGateway);
                list.Dispose();
            });
            
            sw.Stop();
            Console.WriteLine("Created and disposed {0} lists in {1:0.0} seconds.  {2:0.0} ms/list", numLists, sw.Elapsed.TotalSeconds, ((float)sw.ElapsedMilliseconds) / numLists);
            Thread.Sleep(1000);
            sw.Restart();
            */

            Console.WriteLine("Manual threading attempt!");
            Thread[] threads = new Thread[numLists];
            for (int i = 0; i < numLists; i++)
            {
                threads[i] = new Thread((state) =>
                {
                    int listNum = (int)state;
                    //if (listNum % 100 == 0)
                    //    Console.WriteLine("  On list {0}", listNum);
                    CloudList<int> list = new CloudList<int>(fabricAddress, "list WinFabPart Group", "list Creation " + listNum, numPartitions, useGateway);
                    list.Dispose();
                    //Console.Write(".");
                });
                threads[i].Start(i);
            }
            foreach (Thread t in threads)
                t.Join();

            sw.Stop();
            //Console.WriteLine();
            Console.WriteLine("Created and disposed {0} lists in {1:0.0} seconds.  {2:0.0} ms/list", numLists, sw.Elapsed.TotalSeconds, ((float)sw.ElapsedMilliseconds) / numLists);
        }

#if CUSTOM_PARTITIONING_AND_CONNECTION_SHARING
        public static void CloudListPartitioningPerfTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("CloudList Partitioning perf test");
            const int numPartitions = 10;
            Func<String, String> partitioner = listName => "listPartition" + ((uint)listName.GetHashCode()) % numPartitions;

            ThreadPool.SetMinThreads(100, 30);

            int numLists = 500;
            int numElements = 10;
            Console.WriteLine("Creating {0} lists, adding {1} elements to each list, etc across {2} partitions.", numLists, numElements, numPartitions);

            Stopwatch sw = new Stopwatch();
            sw.Start();

            // The sequential version of the loop takes about 17 seconds in VS, 10 seconds out of VS.
            // The parallel version?  137 seconds in VS, 37 seconds out of VS.
            //Parallel.For(0, numLists, listNum =>
            for(int listNum = 0; listNum < numLists; listNum++)
            {
                CloudList<int> list = new CloudList<int>(fabricAddress, "list Add " + listNum, partitioner, useGateway);
                list.Clear();
                int[] array = new int[numElements];
                for (int i = 0; i < numElements; i++)
                    array[i] = i + listNum;
                list.AddAsync(array).Wait();
                list.SortAsync().Wait();
                int[] contents = list.ToArray();
                for (int i = 0; i < numElements; i++)
                    if (array[i] != contents[i])
                        throw new Exception(String.Format("Found incorrect item in sorted array!  List number: {0} Index: {1}  Expected: {2}  Got: {3}", listNum, i, array[i], contents[i]));
                list.Clear();
            }
            sw.Stop();
            Console.WriteLine("Created, added, sorted & verified {0} lists in {1:0.0} seconds.  {2} ms/list",
                              numLists, sw.Elapsed.TotalSeconds, ((float)sw.ElapsedMilliseconds) / numLists);            
        }
#endif

        public static void CloudListPartitioningWinFabStylePerfTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("CloudList WinFab-Style Partitioning perf test");
            const int numPartitions = 10;

            int numLists = 500;
            int numElements = 10;
            Console.WriteLine("Creating {0} lists, adding {1} elements to each list, etc across {2} partitions.", numLists, numElements, numPartitions);

            Stopwatch sw = new Stopwatch();
            sw.Start();

            // The sequential version of the loop takes about 17 seconds in VS, 10 seconds out of VS.
            // The parallel version?  137 seconds in VS, 37 seconds out of VS.
            //Parallel.For(0, numLists, listNum =>
            for (int listNum = 0; listNum < numLists; listNum++)
            {
                CloudList<int> list = new CloudList<int>(fabricAddress, "list WinFab Add group", "list " + listNum, numPartitions, useGateway);
                list.Clear();
                int[] array = new int[numElements];
                for (int i = 0; i < numElements; i++)
                    array[i] = i + listNum;
                list.AddAsync(array).Wait();
                list.SortAsync().Wait();
                int[] contents = list.ToArray();
                for (int i = 0; i < numElements; i++)
                    if (array[i] != contents[i])
                        throw new Exception(String.Format("Found incorrect item in sorted array!  List number: {0} Index: {1}  Expected: {2}  Got: {3}", listNum, i, array[i], contents[i]));
                list.Clear();
                list.Dispose();
            }
            sw.Stop();
            Console.WriteLine("Created, added, sorted & verified {0} lists in {1:0.0} seconds.  {2} ms/list", numLists, sw.Elapsed.TotalSeconds, ((float)sw.ElapsedMilliseconds) / numLists);
        }

        // Create some list actors on a partition, then have clients simultaneously blast them all with data and
        // read it back.  This should test that partitioning is working and that the individual actor states
        // are properly isolated.
        public static void CloudListPartitioningWinFabStylePerfTest2(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("CloudList WinFab-Style Partitioning perf test 2");
            const int numPartitions = 2; // Minimize the #partitions, so that collision is highly probable.

            int numLists = 10;
            int numElements = 5000;
            Console.WriteLine("Creating {0} lists, adding {1} elements to each list, etc across {2} partitions.", numLists, numElements, numPartitions);

            // Create the lists sequentially...
            CloudList<int>[] lists = new CloudList<int>[numLists];
            for (int listNum = 0; listNum < numLists; listNum++)
            {
                lists[listNum] = new CloudList<int>(fabricAddress, "PartitioningPerfGroup", "list" + listNum, numPartitions, useGateway);
            }

            // Now that all of the lists have been created, let's kick off some threads to access them simultaneously
            // (Use actual threads because Parallel.For seems to behave really badly in terms of perf.)
            Thread[] runThreads = new Thread[numLists];
            int numErrors = 0;
            for (int i = 0; i < numLists; i++)
            {
                runThreads[i] = new Thread(obj =>
                    {
                        // Figure out who you are
                        int myIndex = (int)obj;
                        int myBase = myIndex * numElements;
                        CloudList<int> myList = lists[myIndex];

                        // hack for testing
                        if (myIndex == 1)
                            Thread.Sleep(1);

                        // Blast out numElements writes
                        Console.WriteLine("Runner {0} beginning writes...", myIndex);
                        for (int j = 0; j < numElements; j++) myList.Add(myBase + j);

                        // Read them back en masse, and check the result
                        Console.WriteLine("Runner {0} done writing, starting check...", myIndex);
                        int[] array = myList.ToArray();
                        bool error = false;
                        if (array.Length != numElements)
                        {
                            Console.WriteLine("Runner {0}: Expected {1} elements on readback, got {2}  First element: {3}  Expected: {4}  Last element: {5}", 
                                myIndex, numElements, array.Length, array[0], myBase, array[array.Length - 1]);
                            Interlocked.Increment(ref numErrors);
                            error = true;
                        }
                        for(int j=0; j<numElements; j++) 
                        {
                            if (array[j] != myBase + j)
                            {
                                Console.WriteLine("Runner {0}: Element mismatch at index {1}: got {2} exp {3}", myIndex, j, array[j], myBase + j);
                                Interlocked.Increment(ref numErrors);
                                error = true;
                                break;
                            }
                        }

                        if (!error)
                            Console.WriteLine("Runner {0} done.", myIndex);
                        else
                            Console.WriteLine("Runner {0} failed horribly.", myIndex);
                    });
            }

            Stopwatch sw = new Stopwatch();
            sw.Start();

            // Now start the threads.  Doing in this order to try to increase the chances of seeing hypothetical races.
            for(int i = 0; i<numLists; i++)
            {
                runThreads[i].Start(i);
            }

            // Wait for runners to complete
            for (int i = 0; i < numLists; i++) runThreads[i].Join();
            Console.WriteLine("All runners complete.");

            sw.Stop();
            Console.WriteLine("Total time for {0} runners to complete: {1} ms", numLists, sw.ElapsedMilliseconds);

            // Delete lists
            for (int i = 0; i < numLists; i++)
            {
                var list = lists[i];
                list.Clear();
                if (i == numLists - 1) list.Destroy();
                list.Dispose();
            }

            if (numErrors > 0) throw new Exception("Finished with errors.  See spew.");

            Console.WriteLine("Done clearing/disposing/deleting lists.");
        }

        public static void NonReplicatingPartitionedListTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Stateless Partitioned List test");
            const int numPartitions = 2; // Minimize the #partitions, so that collision is highly probable.

            int numLists = 10;
            int numElements = 5000;
            Console.WriteLine("Creating {0} lists, adding {1} elements to each list, etc across {2} partitions.", numLists, numElements, numPartitions);

            var partitionedListName = "partitionedStatelessList";
            EnsureListCreated(fabricAddress, partitionedListName, 1, true, 2);
            string actorName = "fabric:/actor/stateless/" + partitionedListName;
            Uri fabricUri = new Uri(@"net.tcp://" + fabricAddress);
            Uri actorUri = new Uri(actorName);

            // Create clients sequentially
            FabricActorClient[] clients = new FabricActorClient[numLists];
            for (int i = 0; i < numLists; i++)
            {
                clients[i] = new FabricActorClient(fabricUri, actorUri, useGateway, "list"+i);
            }

            Stopwatch sw = new Stopwatch();
            sw.Start();

            // Now that all of the clients have been created, let's kick off some threads to access them simultaneously
            // (Use actual threads because Parallel.For seems to behave really badly in terms of perf.)
            Thread[] runThreads = new Thread[numLists];
            int numErrors = 0;
            for (int i = 0; i < numLists; i++)
            {
                runThreads[i] = new Thread(obj =>
                {
                    // Figure out who you are
                    int myIndex = (int)obj;
                    int myBase = myIndex * numElements;
                    FabricActorClient myList = clients[myIndex];
                    string sublistName = "list"+myIndex;

                    // Blast out numElements writes
                    Console.WriteLine("Runner {0} beginning writes...", myIndex);
                    for (int j = 0; j < numElements; j++) myList.CallMethod("Push", new object[] {sublistName, "right",  myBase + j});

                    // Read them back en masse, and check the result
                    Console.WriteLine("Runner {0} done writing, starting check...", myIndex);
                    int[] array = myList.CallMethod<int[]>("Enumerate", new object[] {sublistName, 0, -1});
                    if (array.Length != numElements)
                    {
                        Console.WriteLine("Runner {0}: Expected {1} elements on readback, got {2}", myIndex, numElements, array.Length);
                        Interlocked.Increment(ref numErrors);
                        return;
                    }
                    for (int j = 0; j < numElements; j++)
                    {
                        if (array[j] != myBase + j)
                        {
                            Console.WriteLine("Runner {0}: Element mismatch at index {1}: got {2} exp {3}", myIndex, j, array[j], myBase + j);
                            Interlocked.Increment(ref numErrors);
                            return;
                        }
                    }

                    Console.WriteLine("Runner {0} done.", myIndex);
                });
                runThreads[i].Start(i);
            }

            // Wait for runners to complete
            for (int i = 0; i < numLists; i++) runThreads[i].Join();
            Console.WriteLine("All runners complete.");

            sw.Stop();
            Console.WriteLine("Total time for {0} runners to complete: {1} ms", numLists, sw.ElapsedMilliseconds);

            // Delete clients
            for (int i = 0; i < numLists; i++)
            {
                var client = clients[i];
                client.Dispose();
            }


            if (numErrors > 0) throw new Exception("Finished with errors.  See spew.");
            else
            {
                FabricActorClient.DeleteActor(fabricAddress, actorName);
                Console.WriteLine("Done clearing/disposing/deleting lists.");
            }
        }
        #endregion CloudList<T> tests

    }
}
